(function(window){

	PlanetaryPositions.epoch = new Date(1999, 11, 31, 0, 0, 0, 0);
	PlanetaryPositions.YEAR_DAYS = 365.256898326;
	PlanetaryPositions.PLANET_NAMES = ["mercury", "venus", "earth", "moon", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];

	PlanetaryPositions.MOON_SYNODIC_PERIOD = 29.5306;
	PlanetaryPositions.MOON_SIDEREAL_PERIOD = 27.32121599478479;
	// PlanetaryPositions.normaliseExclude = ["a", "e", "x", "y", "d", "Dec", "RightAscension", "Declination", "altitude", "azimuth"];

	PlanetaryPositions.simple = false;
	PlanetaryPositions.lastDate = new Date();
	PlanetaryPositions.lastSunDate = new Date();
	PlanetaryPositions.lastSun;
	PlanetaryPositions.lastLST;
	PlanetaryPositions.longitude = -2.583;//15;//
	PlanetaryPositions.latitude  = 51.467;//60;//

	function PlanetaryPositions(){	
		
	}
	PlanetaryPositions.sun = function(date) {
		var d = PlanetaryPositions.getDays(date)>>0;
		var N = 0.0;
		var i = 0.0;
		var w = NumberUtils.normaliseDegrees(282.9404 + 4.70935E-5 * d);
		var a = 1.000000;
		var e = 0.016709 - 1.151E-9 * d;
		var M = NumberUtils.normaliseDegrees(356.0470 + 0.9856002585 * d);
		var ecl = 23.4393 - (3.563E-7 * d);
		var E = PlanetaryPositions.getE(M, e);
		var rv = PlanetaryPositions.planetDistanceAndTrueAnomaly(E, e, a);
		var lonsun = rv.v + w;
		if(!PlanetaryPositions.simple){
			var L = w + M;
			var xs = rv.r * NumberUtils.cosD(lonsun);
			var ys = rv.r * NumberUtils.sinD(lonsun);
			var xe = xs;
			var ye = ys * NumberUtils.cosD(ecl);
			var ze = ys * NumberUtils.sinD(ecl);
			var RA  = NumberUtils.normaliseDegrees(NumberUtils.atan2D( ye, xe ));
			var Dec = NumberUtils.atan2D( ze, Math.sqrt(xe*xe+ye*ye) );
			var LST
			if(PlanetaryPositions.lastSunDate.getTime() != date.getTime() || !PlanetaryPositions.lastLST){
				PlanetaryPositions.lastSunDate = date;
				PlanetaryPositions.lastLST = PlanetaryPositions.localSiderealTime(date, lonsun);
				PlanetaryPositions.lastSun = PlanetaryPositions.sun(date);
			}
			LST = PlanetaryPositions.lastLST;
			var HA = NumberUtils.normaliseDegrees(LST*15 - RA);
			
			var aax = NumberUtils.cosD(HA) * NumberUtils.cosD(Dec);
			var aay = NumberUtils.sinD(HA) * NumberUtils.cosD(Dec);
			var aaz = NumberUtils.sinD(Dec);
			
			var xhor = aax * NumberUtils.sinD(PlanetaryPositions.latitude) - aaz * NumberUtils.cosD(PlanetaryPositions.latitude);
			var yhor = aay;
			var zhor = aax * NumberUtils.cosD(PlanetaryPositions.latitude) + aaz * NumberUtils.sinD(PlanetaryPositions.latitude);

			var azimuth  = NumberUtils.atan2D( yhor, xhor ) + 180;
			var altitude = NumberUtils.atan2D( zhor, Math.sqrt(xhor*xhor+yhor*yhor) );
			
			var o = {
				d : d,
				L : L,
				x : xe,
				y : ye,
				z : ze,
				r : rv.r,
				lonsun : lonsun,
				RA : RA,
				Dec : Dec,
				LST : LST,
				HA : HA,
				azimuth : azimuth,
				altitude : altitude,
				RightAscension : (NumberUtils.degreesRightAscensionToHMS(NumberUtils.normaliseDegrees(RA)).string),
				Declination : (NumberUtils.degreesDeclinationToHMS(Dec).string)
			};
		} else {
			var o = {
					lonsun : lonsun
				};
		}
		return o;//pp.normaliseDegrees(o);
	};
	PlanetaryPositions.mercury = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = (48.3313 + 3.24587E-5 * d);
		oe.i = (7.0047 + 5.00E-8 * d);
		oe.w = NumberUtils.normaliseDegrees(29.1241 + 1.01444E-5 * d);
		oe.a = (0.387098);
		oe.e = (0.205635 + 5.59E-10 * d);
		oe.M = NumberUtils.normaliseDegrees(168.6562 + 4.0923344368 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.venus = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(76.6799 + 2.46590E-5 * d);
		oe.i = (3.3946 + 2.75E-8 * d);
		oe.w = NumberUtils.normaliseDegrees(54.8910 + 1.38374E-5 * d);
		oe.a = (0.723330);
		oe.e = (0.006773 - 1.302E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(48.0052 + 1.6021302244 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.earth = function(date) {
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = (0.0);
		oe.i = (0.0);
		oe.w = NumberUtils.normaliseDegrees(282.9404 + 4.70935E-5 * d);
		oe.a = (1.000000);
		oe.e = (0.016709 - 1.151E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(356.0470 + 0.9856002585 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		var ro = PlanetaryPositions.getPosition(oe);
		ro.lonecl+=180;
		return NumberUtils.normaliseDegrees(ro);
	};
	
	PlanetaryPositions.moon = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(125.1228 - 0.0529538083 * d);
		oe.i = (5.1454);
		oe.w = NumberUtils.normaliseDegrees(318.0634 + 0.1643573223 * d);
		oe.a = (60.2666);
		oe.e = (0.054900);
		oe.M = NumberUtils.normaliseDegrees(115.3654 + 13.0649929509 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe, "moon");
	};
	
	PlanetaryPositions.mars = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(49.5574 + 2.11081E-5 * d);
		oe.i = (1.8497 - 1.78E-8 * d);
		oe.w = NumberUtils.normaliseDegrees(286.5016 + 2.92961E-5 * d);
		oe.a = (1.523688);
		oe.e = (0.093405 + 2.516E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(18.6021 + 0.5240207766 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.jupiter = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(100.4542 + 2.76854E-5 * d);
		oe.i = (1.3030 - 1.557E-7 * d);
		oe.w = NumberUtils.normaliseDegrees(273.8777 + 1.64505E-5 * d);
		oe.a = (5.20256);
		oe.e = (0.048498 + 4.469E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(19.8950 + 0.0830853001 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.saturn = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(113.6634 + 2.38980E-5 * d);
		oe.i = (2.4886 - 1.081E-7 * d);
		oe.w = NumberUtils.normaliseDegrees(339.3939 + 2.97661E-5 * d);
		oe.a = (9.55475);
		oe.e = (0.055546 - 9.499E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(316.9670 + 0.0334442282 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.uranus = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees( 74.0005 + 1.3978E-5 * d);
		oe.i = (0.7733 + 1.9E-8 * d);
		oe.w = NumberUtils.normaliseDegrees( 96.6612 + 3.0565E-5 * d);
		oe.a = NumberUtils.normaliseDegrees(19.18171 - 1.55E-8 * d);
		oe.e = (0.047318 + 7.45E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(142.5905 + 0.011725806 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	
	PlanetaryPositions.neptune = function(date){
		var d = PlanetaryPositions.getDays(date);
		var oe = new OrbitalElements();
		oe.N = NumberUtils.normaliseDegrees(131.7806 + 3.0173E-5 * d);
		oe.i = (1.7700 - 2.55E-7 * d);
		oe.w = NumberUtils.normaliseDegrees(272.8461 - 6.027E-6 * d);
		oe.a = NumberUtils.normaliseDegrees(30.05826 + 3.313E-8 * d);
		oe.e = (0.008606 + 2.15E-9 * d);
		oe.M = NumberUtils.normaliseDegrees(260.2471 + 0.005995147 * d);
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe);
	};
	PlanetaryPositions.pluto = function(date){
		var d = PlanetaryPositions.getDays(date)
		var oe = new OrbitalElements();
		oe.ecl = 23.4393 - (3.563E-7 * d);
		oe.days = PlanetaryPositions.getDays(date);
		oe.date = date;
		return PlanetaryPositions.getPosition(oe, "pluto");
	}

	PlanetaryPositions.localSiderealTime = function(date, lonsun){
		var GMST0 = NumberUtils.normaliseDegrees(lonsun+180)/15;
		var UT = date.getUTCHours()+((date.getUTCMinutes()/60)+(date.getUTCSeconds()/3600));
		return NumberUtils.normaliseHours(GMST0 + UT + (PlanetaryPositions.longitude/15));
	}
	PlanetaryPositions.planetDistanceAndTrueAnomaly = function(E , e , a )  {
		var xv = a * (NumberUtils.cosD(E) - e);
		var yv = a * (Math.sqrt(1.0 - e * e) * NumberUtils.sinD(E));
		var v = NumberUtils.atan2D( yv, xv);
		var r = Math.sqrt(xv * xv + yv * yv);
		var o  = {};
		o.r = r;
		o.v = v;
		return o;
	};
	
	PlanetaryPositions.planetHeliocentricPosition = function(N , w , i , rv)  {
		var xh = rv.r * ( NumberUtils.cosD(N) * NumberUtils.cosD(rv.v+w) - NumberUtils.sinD(N) * NumberUtils.sinD(rv.v+w) * NumberUtils.cosD(i) );
	    var yh = rv.r * ( NumberUtils.sinD(N) * NumberUtils.cosD(rv.v+w) + NumberUtils.cosD(N) * NumberUtils.sinD(rv.v+w) * NumberUtils.cosD(i) );
	    var zh = rv.r * ( NumberUtils.sinD(rv.v+w) * NumberUtils.sinD(i) );
		var lonecl  = NumberUtils.atan2D( yh, xh );
		var latecl = NumberUtils.atan2D( zh, Math.sqrt(xh*xh+yh*yh) );
		var o = {
			lonecl : lonecl,
			latecl : latecl
		};
		return o;
	};
	PlanetaryPositions.plutoHeliocentricPosition = function(d){
		
	    var S  =   50.03  +  0.033459652 * d;
	    var P  =  238.95  +  0.003968789 * d;


	    var lonecl = 238.9508  +  0.00400703 * d
	            - 19.799 * NumberUtils.sinD(P)     + 19.848 * NumberUtils.cosD(P)
	             + 0.897 * NumberUtils.sinD(2*P)    - 4.956 * NumberUtils.cosD(2*P)
	             + 0.610 * NumberUtils.sinD(3*P)    + 1.211 * NumberUtils.cosD(3*P)
	             - 0.341 * NumberUtils.sinD(4*P)    - 0.190 * NumberUtils.cosD(4*P)
	             + 0.128 * NumberUtils.sinD(5*P)    - 0.034 * NumberUtils.cosD(5*P)
	             - 0.038 * NumberUtils.sinD(6*P)    + 0.031 * NumberUtils.cosD(6*P)
	             + 0.020 * NumberUtils.sinD(S-P)    - 0.010 * NumberUtils.cosD(S-P);

	    var latecl =  -3.9082
	             - 5.453 * NumberUtils.sinD(P)     - 14.975 * NumberUtils.cosD(P)
	             + 3.527 * NumberUtils.sinD(2*P)    + 1.673 * NumberUtils.cosD(2*P)
	             - 1.051 * NumberUtils.sinD(3*P)    + 0.328 * NumberUtils.cosD(3*P)
	             + 0.179 * NumberUtils.sinD(4*P)    - 0.292 * NumberUtils.cosD(4*P)
	             + 0.019 * NumberUtils.sinD(5*P)    + 0.100 * NumberUtils.cosD(5*P)
	             - 0.031 * NumberUtils.sinD(6*P)    - 0.026 * NumberUtils.cosD(6*P)
	                                   + 0.011 * NumberUtils.cosD(S-P);

	   var r     =  40.72
	           + 6.68 * NumberUtils.sinD(P)       + 6.90 * NumberUtils.cosD(P)
	           - 1.18 * NumberUtils.sinD(2*P)     - 0.03 * NumberUtils.cosD(2*P)
	           + 0.15 * NumberUtils.sinD(3*P)     - 0.14 * NumberUtils.cosD(3*P);

		var o = {
				lonecl : lonecl,
				latecl : latecl,
				r : r
			};
			return o;
			
	}
	// PlanetaryPositions.isNotNormaliseExcluded = function(i )  {
	// 	for(var str in PlanetaryPositions.normaliseExclude){
	// 		if(PlanetaryPositions.normaliseExclude[str] == i) return false;
	// 	}
	// 	return true;
	// };
	// PlanetaryPositions.normaliseDegrees = function(o) {	
	// 	for (var i in o){
	// 		if (PlanetaryPositions.isNotNormaliseExcluded(i)) {
	// 			o[i] %= 360;
	// 			if(o[i] < 0){
	// 				o[i] += 360;
	// 			}
	// 		}
	// 	}
	// 	return o;
	// };
	PlanetaryPositions.getE = function(M, e, isPlanet) {
		var rE = M + e * (180 / Math.PI) * NumberUtils.sinD(M) * (1 + e * NumberUtils.cosD(M));
		var E1;
		var E0;
		if (isPlanet) {
			if (e > 0.06) {
				E0 = rE;
				E1 = E0 - (E0 - (180 / Math.PI) * e * NumberUtils.sinD(E0) - M) / (1 - e * NumberUtils.cosD(E0));
				while (E1 - E0 > 5E-3){
					E0 = E1;
					E1 = E0 - (E0 - (180 / Math.PI) * e * NumberUtils.sinD(E0) - M) / (1 - e * NumberUtils.cosD(E0));
				}
				rE = E1;
			}
		}
		return rE;
	}
	PlanetaryPositions.getDays = function(date) {
		var Y = date.getFullYear();
		var M = date.getUTCMonth()+1;
		var D = date.getUTCDate();
		var d = 367 * Y - (7 * (Y + ((M + 9) / 12))) / 4 + (275 * M) / 9 + D - 730530;
		//var UT = (date.getUTCHours() + (date.getUTCMinutes() / 60)) / 24.0;
		//d += UT;
		var altD = ((date.getTime()-PlanetaryPositions.epoch.getTime())/(1000*60*60*24));
		return altD;
	};
	
	PlanetaryPositions.azimuthAltitude = function(RA, Dec, date){
		var sun
		if(PlanetaryPositions.lastDate.getTime() != date.getTime() || !PlanetaryPositions.lastSun){
			PlanetaryPositions.lastDate = date;
			PlanetaryPositions.lastSun = PlanetaryPositions.sun(date);
		}
		sun = PlanetaryPositions.lastSun;
		var rs = sun.r;
		var lonsun = sun.lonsun;
		var LST
		 if(PlanetaryPositions.lastDate.getTime() != date.getTime() || !PlanetaryPositions.lastLST){
				PlanetaryPositions.lastDate = date;
				PlanetaryPositions.lastLST = PlanetaryPositions.localSiderealTime(date, lonsun);
			}
		 LST = PlanetaryPositions.lastLST;
		 var HA = NumberUtils.normaliseDegrees(LST*15 - RA*15);
		 
		 var aax = NumberUtils.cosD(HA) * NumberUtils.cosD(Dec);
		 var aay = NumberUtils.sinD(HA) * NumberUtils.cosD(Dec);
		 var aaz = NumberUtils.sinD(Dec);
		 
		 var xhor = aax * NumberUtils.sinD(PlanetaryPositions.latitude) - aaz * NumberUtils.cosD(PlanetaryPositions.latitude);
		 var yhor = aay;
		 var zhor = aax * NumberUtils.cosD(PlanetaryPositions.latitude) + aaz * NumberUtils.sinD(PlanetaryPositions.latitude);

		 var azimuth  = NumberUtils.atan2D( yhor, xhor ) + 180;
		 var altitude = NumberUtils.atan2D( zhor, Math.sqrt(xhor*xhor+yhor*yhor) );
		 return {
			 azimuth : azimuth, 
			 altitude : altitude,
			 LST : LST,
			 HA : HA
			};
	}

	PlanetaryPositions.getPosition = function(oe, special){
		var N = oe.N;
		var i = oe.i;
		var w = oe.w;
		var a = oe.a;
		var e = oe.e;
		var M = oe.M;
		var ecl = oe.ecl;
		var E  = PlanetaryPositions.getE(M, e, true);
		var rv
		var hp
		if(special == "pluto"){
			hp = PlanetaryPositions.plutoHeliocentricPosition(oe.days);

			rv = {r:hp.r};//PlanetaryPositions.planetDistanceAndTrueAnomaly(E, e, a);
		} else {
			rv = PlanetaryPositions.planetDistanceAndTrueAnomaly(E, e, a);
			hp = PlanetaryPositions.planetHeliocentricPosition(N, w, i, rv);
		}
		if(!PlanetaryPositions.simple){
			var xh = rv.r * NumberUtils.cosD(hp.lonecl) * NumberUtils.cosD(hp.latecl);
		    var yh = rv.r * NumberUtils.sinD(hp.lonecl) * NumberUtils.cosD(hp.latecl);
		    var zh = rv.r * NumberUtils.sinD(hp.latecl);
			var sun
			//console.log(PlanetaryPositions.lastDate.getTime()+" != "+oe.date.getTime())
			if(PlanetaryPositions.lastDate.getTime() != oe.date.getTime() || !PlanetaryPositions.lastSun){
				//console.log("NEW SUN")
				PlanetaryPositions.lastDate = oe.date;
				PlanetaryPositions.lastSun = PlanetaryPositions.sun(oe.date);
			}
			sun = PlanetaryPositions.lastSun;
			var rs = sun.r;
			var lonsun = sun.lonsun;
			
			var xs = rs * NumberUtils.cosD(lonsun);
			var ys = rs * NumberUtils.sinD(lonsun);
			var xg = xh + xs;
			var yg = yh + ys;
			var zg = zh;
			if(special == "moon"){
			 xg = xh;
			 yg = yh;
			 zg = zh;
			} else {

			 //console.log("is not moon")
			 xg = xh + xs;
			 yg = yh + ys;
			 zg = zh;
			}


			var xe = xg;
			var ye = yg * NumberUtils.cosD(ecl) - zg * NumberUtils.sinD(ecl);
			var ze = yg * NumberUtils.sinD(ecl) + zg * NumberUtils.cosD(ecl);

			var RA  = NumberUtils.normaliseDegrees(NumberUtils.atan2D( ye, xe ))/15;
			var Dec = NumberUtils.atan2D( ze, Math.sqrt(xe*xe+ye*ye) );
			var rg = Math.sqrt(xe*xe+ye*ye+ze*ze);
			var aa = PlanetaryPositions.azimuthAltitude(RA, Dec, oe.date)

			if(special == "moon"){
				var mpar = NumberUtils.asinD(1/rg);
				var gclat = PlanetaryPositions.latitude - 0.1924 * NumberUtils.sinD(2* PlanetaryPositions.latitude);
				var rho = 0.99833 + 0.00167 * NumberUtils.cosD(2 * PlanetaryPositions.latitude);
				var g = NumberUtils.atanD(NumberUtils.tanD(gclat)/NumberUtils.cosD(aa.HA));
				RA = RA - mpar * rho * NumberUtils.cosD(gclat) * NumberUtils.sinD(aa.HA) / NumberUtils.cosD(Dec);
				Decl = Dec - mpar * rho * NumberUtils.sinD(gclat) * NumberUtils.sinD(g - Dec) / NumberUtils.sinD(g);
				var moonSunAngle = NumberUtils.normaliseDegrees(360-(aa.HA-sun.HA))/(360/PlanetaryPositions.MOON_SYNODIC_PERIOD);
				//console.log(sun)
				var moonPhase = Math.round(moonSunAngle)
			}

			var o = {
				moonPhase : moonPhase,
				lonecl : hp.lonecl,
				latecl : hp.latecl,
				r : rg,
				RA : RA,
				Dec : Dec,
				LST : aa.LST,
				HA : aa.HA,
				azimuth : aa.azimuth,
				altitude : aa.altitude,
				RightAscension : (NumberUtils.degreesRightAscensionToHMS(RA).string),
				Declination : (NumberUtils.degreesDeclinationToHMS(Dec).string)
			};
		} else {
			var o = {
					lonecl : hp.lonecl
				};
		}
		return o;//pp.normaliseDegrees(o);
	};
	function OrbitalElements() {
		var oe = this;
		oe.name;
		oe.N;
		oe.i;
		oe.w;
		oe.a;
		oe.e;
		oe.M;
		oe.ecl;
		oe.days;
		oe.date;
	}
	if(!window.PlanetaryPositions){window.PlanetaryPositions = PlanetaryPositions};	
}(window))